// Dictionary.cpp
//
// Copyright (c) 2008 Bojan Rajkovic
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
//

#include "../include/Dictionary.h"

using namespace UltraHAL;

Dictionary::Dictionary(std::vector<std::string> Words) {
	words = Words;
	PopulateSoundexMap();
}

Dictionary::Dictionary(void) {
	PopulateSoundexMap();
}

Dictionary::Dictionary(std::string DictionaryFile) {
	Load(DictionaryFile);
	PopulateSoundexMap();
}

void Dictionary::PopulateSoundexMap(void) {
	// Populate the character map for soundex.
	soundexCharacterMap['A'] = 0;
	soundexCharacterMap['E'] = 0;
	soundexCharacterMap['I'] = 0;
	soundexCharacterMap['O'] = 0;
	soundexCharacterMap['U'] = 0;
	soundexCharacterMap['H'] = 0;
	soundexCharacterMap['W'] = 0;
	soundexCharacterMap['Y'] = 0;
	soundexCharacterMap['B'] = 1;
	soundexCharacterMap['F'] = 1;
	soundexCharacterMap['P'] = 1;
	soundexCharacterMap['V'] = 1;
	soundexCharacterMap['C'] = 2;
	soundexCharacterMap['G'] = 2;
	soundexCharacterMap['J'] = 2;
	soundexCharacterMap['K'] = 2;
	soundexCharacterMap['Q'] = 2;
	soundexCharacterMap['S'] = 2;
	soundexCharacterMap['X'] = 2;
	soundexCharacterMap['Z'] = 2;
	soundexCharacterMap['D'] = 3;
	soundexCharacterMap['T'] = 3;				
	soundexCharacterMap['L'] = 4;
	soundexCharacterMap['M'] = 5;
	soundexCharacterMap['N'] = 5;
	soundexCharacterMap['R'] = 6;
}

uint64_t Dictionary::AddWord(std::string Word) {
	// If we have the word already, just shoot back its position.
	int64_t wordPosition = FindWord(Word);
	if (wordPosition > -1) return wordPosition;
	/* This makes g++ bitch about a C99 long long integer constant, but if I can't get the #define from stdint.h,
	I'll just define it myself in Dictionary.h. Also, if you have a word bigger than UINT64_MAX letters, may 
	whatever deity you ascribe to save your soul. */
	if (Word.length() > UINT64_MAX) return -1;
	words.push_back(Word);
	// Populate the first characters map.
	std::pair<uint64_t,uint64_t> lengthPair(Word.length(),Word.length());
	std::pair<std::pair<uint64_t,uint64_t>, bool> lookupPair(lengthPair, true);
	firstCharacterMap[toupper(Word[0])].push_back(Word);
	// Populate the static length map.
	lengthMap[lookupPair].push_back(Word);
	/* Send back the word's position in the entries array. We know this has got to be located at the last spot, 
	which is size-1.*/
	return words.size()-1;
}

void Dictionary::Sort() {
	std::sort(words.begin(), words.end());
	positionMap.clear();
}

std::string Dictionary::GetWord(uint64_t Position) {
	return words.at(Position);
}

void Dictionary::Load(std::string File) {
	std::ifstream inFile(File.c_str());
	std::string fileLine;
	while (getline(inFile, fileLine)) {
		if (fileLine.length() > 0) AddWord(fileLine);
	}
}

int64_t Dictionary::FindWord(std::string Word) {
	/* We'll speed this up a bit with a map for caching. If we don't have it in the map, check if we have it at all.
	If we don't have it in the map, let's put it in the map. */
	if (positionMap[Word] == 0) { 
		if (!Dictionary::HasWord(Word)) return -1;
		std::vector<std::string>::iterator wordsIterator = find(words.begin(), words.end(), Word);
		positionMap[Word] = static_cast<int>(wordsIterator - words.begin());
	}
	// Return the position from the map.
	return positionMap[Word];
}

bool Dictionary::HasWord(std::string Word) {
	return std::binary_search(words.begin(), words.end(), Word);
}

void Dictionary::Save(std::string File) {
	std::ofstream outFile(File.c_str(), std::ios::trunc);
	foreach(std::string Word, words)
		outFile << Word << std::endl;
	outFile.close();
}

std::string Dictionary::SoundexKey(std::string Word) {
	/* If we don't have it cached, create it, else pull it from cache. Some words will always have it recalculated
	because their soundex is 0--there's probably a workaround for this, but I don't see one right now. */
	if (soundexKeysMap[Word] == "") {
		std::ostringstream soundexKeyBuilder;
		foreach(char character, Word) {
			if (soundexCharacterMap[toupper(character)]) {
				soundexKeyBuilder << soundexCharacterMap[toupper(character)];
			} else {
				soundexKeyBuilder << 0;
			}
		}
		std::string soundexKey = soundexKeyBuilder.str();
		// boost's algorithm::replace_all swaps all occurences of parameter 2 with parameter 3
		boost::algorithm::replace_all(soundexKey,"0","");
		// Somewhat of a *HACK* here to remove duplicates from a string. Might be done more elegantly with
		// iterators, but who cares. :}
		std::ostringstream soundexUnique;
		foreach (char character, soundexKey) {
			char last = soundexUnique.str()[soundexUnique.str().length()-1];
			if (last != character) {
				soundexUnique << character;
			}
		}
		soundexKeysMap[Word] = soundexUnique.str();
	}
	return soundexKeysMap[Word];
}

// The following 4 functions are all in reality returning std::vector<std::string>, but since we have a copy constructor
// that takes std::vector<std::string>, we can make an implicit conversion to dictionary. This allows the chaining of
// selection operations, like selecting words by character set, and then by length, or by length and then by soundex,
// ad nauseam.

// Just call the long versions with the same minimum and maximum length and inclusive set to true to select only words
// of length N.
Dictionary Dictionary::SelectWordsByLength(uint64_t Length) {
	return SelectWordsByLength(Length, Length, true);
} 

// The inclusive parameter tells us if we're doing [Min,Max] or (Min,Max) searching (ie. are we including the two 
// extremes, or not.
Dictionary Dictionary::SelectWordsByLength(uint64_t MinimumLength, uint64_t MaximumLength, bool Inclusive) {
	std::pair<uint64_t,uint64_t> lengthPair(MinimumLength, MaximumLength);
	std::pair<std::pair<uint64_t,uint64_t>, bool> lookupPair(lengthPair, Inclusive);
	if (lengthMap[lookupPair].size() == 0) {
		foreach (std::string dictionaryWord, words) {
			if (Inclusive) {
				if (MinimumLength <= dictionaryWord.length() && dictionaryWord.length() <= MaximumLength) {
					lengthMap[lookupPair].push_back(dictionaryWord);
				}
			} else {
				if (MinimumLength < dictionaryWord.length() && dictionaryWord.length() < MaximumLength) {
					lengthMap[lookupPair].push_back(dictionaryWord);
				}
			}
		}
	}
	return lengthMap[lookupPair];
} 

// Select words that sound like a certain word, based on the SOUNDEX algorithm.
Dictionary Dictionary::SelectWordsThatSoundLike(std::string Word) {
	// Do we have the word in the map already? (ie. vector size > 0)
	if (soundexMap[Word].size() == 0) {
		// Well, we don't have it, time to loop over every word.
		foreach (std::string dictionaryWord, words) {
			if (SoundsLike(Word,dictionaryWord)) {
				// The dictionary word sounds like our word, so push it back into the vector of the soundexMap.
				soundexMap[Word].push_back(dictionaryWord);
			}
		}
	}
	return soundexMap[Word];
}

// Select words that have the characters specified in std::string. Unfortunately, this is *slow* - O(n^2) 
Dictionary Dictionary::SelectWordsWithLetters(std::string CharacterList) {
	if (characterMap[CharacterList].size() == 0) {
		foreach (std::string dictionaryWord, words) {
			bool onlyListChars = true;
			foreach (char wordCharacter, dictionaryWord) {
				if (isalpha(wordCharacter)) { // We want to ignore non-alphabetic characters
					// If the character is not in the list, set the onlyListChars variable to false
					if (CharacterList.find(wordCharacter) == std::string::npos) {
						onlyListChars = false;
					}
				}
			}
			if (onlyListChars) {
				characterMap[CharacterList].push_back(dictionaryWord);
			}
		}
	}
	return characterMap[CharacterList];
}

Dictionary Dictionary::SelectWordsByFirstLetter(char FirstLetter) {
	if (isalpha(FirstLetter)) return firstCharacterMap[toupper(FirstLetter)];
	else throw DictionaryException("Cannot select by first letter, parameter is not a letter.");
}
					
// Figure out if two words sound alike
bool Dictionary::SoundsLike(std::string FirstWord, std::string SecondWord) {
	return (SoundexKey(FirstWord) == SoundexKey(SecondWord));
}